/** @file
  Platform memory configuration.

  Copyright (C) 2022 - 2023, Phytium Technology Co., Ltd. All rights reserved.<BR>
  Copyright (c) 2018, GreatWall. All rights reserved.

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/
#include <Library/ArmPlatformLib.h>
#include <Library/ArmSmcLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/HobLib.h>
#include <Library/IoLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/ParameterTable.h>
#include <Library/PcdLib.h>
#include <Library/PssiLib.h>
#include <Library/SerialPortLib.h>
#include <PhytiumAcpiHelp.h>
#include <AcpiPlatform.h>

// Number of Virtual Memory Map Descriptors
#define MAX_VIRTUAL_MEMORY_MAP_DESCRIPTORS 256

// DDR attributes
#define DDR_ATTRIBUTES_CACHED              ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK
#define DDR_ATTRIBUTES_UNCACHED            ARM_MEMORY_REGION_ATTRIBUTE_UNCACHED_UNBUFFERED

#define ALLOCATE_PAGES         4

/**
  Get die bit map.
  If C2C of all die is disable, it means single die mode, and die0 is enabled
  by default. Otherwise, the die is enabled according to C2C enable. 8 die in
  total. Bit(n) means die(n) enable.

  @return    Map    Die bit map.
**/
UINT8
GetDieMap (
  VOID
  )
{
  UINT8                    Buffer[0x100];
  PARAMETER_C2C_CONFIG     *C2CConfig;
  UINT8                    Map;
  UINT32                   Index;

  Map = 0;
  ZeroMem (Buffer, 0x100);
  //
  //Get C2C information form parameter table.
  //
  if (EFI_SUCCESS == GetParameterInfo (PM_C2C, Buffer, 0x100)) {
  } else {
    DEBUG ((DEBUG_ERROR, "Get C2C Parameter Fail.\n"));
    Map = 0;
    goto ProcExit;
  }

  C2CConfig = (PARAMETER_C2C_CONFIG *) Buffer;
  //
  //If C2C of all die is disable, it means single die mode, and die0 is enabled
  //by default. Otherwise, the die is enabled according to C2C enable.
  //
  if (C2CConfig->C2CEnable.Uint32 == 0) {
    Map = 0x1;
  } else {
    for (Index = 0; Index < 8; Index++) {
      if ((C2CConfig->C2CEnable.Uint32 >> Index * 4) & 0xF) {
        Map |= (0x1 << Index);
      }
    }
  }

ProcExit:
  DEBUG ((DEBUG_INFO, "Die Map : 0x%02x\n", Map));
  return Map;
}

/**
  Build Its Node Info Hob.

  @param [in] SocketCount  Die Count.
**/
VOID
BuildItsInfo (
  IN UINT64 SocketCount
  )
{
  ITS_ACPI_INFO          *ItsAcpiInfo;
  ITS_ACPI_NODE          *ItsAcpiNode;
  UINT32                 ItsAcpiNodeCount;
  UINT32                 Index;
  UINT32                 SocketIndex;
  UINTN                  Size;

  DEBUG ((DEBUG_INFO, "SocketCount:%d\n", SocketCount));
  ItsAcpiInfo = (ITS_ACPI_INFO *)AllocatePages(1);
  Size = EFI_PAGE_SIZE;
  ZeroMem(ItsAcpiInfo, EFI_PAGE_SIZE);

  ItsAcpiNodeCount = SocketCount * ITS_COUNT;
  ItsAcpiInfo->NodeCount = ItsAcpiNodeCount;
  ItsAcpiNode = ItsAcpiInfo->Node;
  for (SocketIndex = 0; SocketIndex < SocketCount; SocketIndex++) {
    for (Index = 0; Index < ITS_COUNT; Index++) {
      ItsAcpiNode->GicItsId =  SocketIndex * ITS_COUNT + Index;
      ItsAcpiNode->PhysicalBaseAddress = SOCKET_ADDRESS_OFFSEST * SocketIndex + ITS_CONTROL_BASE + Index * ITS_CONTROL_OFFSET;
      ItsAcpiNode->ProximityDomain  = SocketIndex;
      ItsAcpiNode++;
    }
  }
  Size = sizeof (UINT32) + ItsAcpiNodeCount * sizeof (ITS_ACPI_NODE);
  BuildGuidDataHob (&gPlatformItsAcpiInfoGuid, ItsAcpiInfo, Size);
  FreePages(ItsAcpiInfo, 1);
}

/**
  Get core Mpidr value based Core Index.

  @param [in] SocketIndex  Die Index.
  @param [in] CoreIndex    Core Index.

  @retval Core Mpidr value.
**/
UINT64
GetMpidrByCoreIndex(UINT32 SocketIndex, UINT32 CoreIndex)
{
  UINT64  Mpidr;

  Mpidr = ((UINT64)SocketIndex << 32) | (CoreIndex << 16) | 0;

  return Mpidr;
}

/**
  Get core GICR Address based Core Index.

  @param [in] SocketIndex  Die Index.
  @param [in] CoreIndex    Core Index.

  @retval Core GICR Address.
**/
UINT64
GetGICRBaseAddress (
  IN UINT32 SocketIndex,
  IN UINT32 CoreIndex
  )
{
  UINT64 GicRBase;

  GicRBase = FixedPcdGet64 (PcdGicRedistributorsBase);

  return GicRBase + SocketIndex * SOCKET_ADDRESS_OFFSEST + CoreIndex * 0x20000;
}

/**
  Build Cpu Acpi Node Info Hob.
**/
VOID
BuildCpuAcpiInfoHob(
  VOID
  )
{
  PHYTIUM_CPU_MAP_INFO          *CpuMapInfo;
  CPU_ACPI_INFO                 *CpuAcpiInfo;
  CPU_ACPI_NODE                 *CpuAcpiNode;
  UINT64                        *CpuMap;
  UINT32                        CoreCount;
  UINT16                        CoreIndex;
  UINT8                         SocketIndex;
  UINT8                         SocketCount;
  UINTN                         Size;

  CoreCount = 0;
  Size = EFI_PAGE_SIZE;

  CpuMapInfo = (PHYTIUM_CPU_MAP_INFO *)AllocatePages(1);
  if (NULL == CpuMapInfo) {
    DEBUG ((DEBUG_ERROR,"Allocate CpuMapInfo Fail\n"));
    while(1);
  }
  ZeroMem(CpuMapInfo, EFI_PAGE_SIZE);
  PssiGetCpuMapsInfo(CpuMapInfo, &Size);
  CpuMap = CpuMapInfo->CpuMap;
  SocketCount = CpuMapInfo->CpuMapCount;

  CpuAcpiInfo = (CPU_ACPI_INFO *)AllocatePages(1);
  if (NULL == CpuAcpiInfo) {
    DEBUG ((DEBUG_ERROR,"Allocate CpuAcpiInfo Fail\n"));
    while(1);
  }

  CpuAcpiNode = CpuAcpiInfo->Node;
  for (SocketIndex = 0; SocketIndex < SocketCount; SocketIndex++) {
    for (CoreIndex = 0; CoreIndex < CPU_COUNT; CoreIndex++) {
      if ((CpuMap[SocketIndex] >> CoreIndex) & 0x1) {
        CpuAcpiNode[CoreCount].AcpiProcessorUid = SocketIndex * CPU_COUNT +  CoreIndex;
        CpuAcpiNode[CoreCount].Mpidr = GetMpidrByCoreIndex (SocketIndex, CoreIndex);
        CpuAcpiNode[CoreCount].GicRBaseAddress =  GetGICRBaseAddress (SocketIndex, CoreIndex);
        CpuAcpiNode[CoreCount].ProximityDomain = SocketIndex;
        CpuAcpiNode[CoreCount].ProcessorPowerEfficiencyClass = 0x0;
        CoreCount++;
      }
    }
  }

  CpuAcpiInfo->NodeCount = CoreCount;
  DEBUG((DEBUG_INFO,"CoreCount = %d\n", CoreCount));
  for (CoreIndex = 0; CoreIndex < CoreCount; CoreIndex++) {
    DEBUG ((
      DEBUG_INFO,
      "Uid:%d, Mpidr:0x%lx, GicRBase:0x%lx, Domain:%d\n",
      CpuAcpiNode->AcpiProcessorUid,
      CpuAcpiNode->Mpidr,
      CpuAcpiNode->GicRBaseAddress,
      CpuAcpiNode->ProximityDomain
    ));
    CpuAcpiNode++;
  }

  Size = sizeof (UINT32) + CoreCount * sizeof (CPU_ACPI_NODE);
  BuildGuidDataHob (&gPlatformCpuAcpiInfoGuid, CpuAcpiInfo, Size);
  FreePages(CpuAcpiInfo, 1);
  FreePages(CpuMapInfo, 1);
}

/**
  Return the Virtual Memory Map of your platform

  This Virtual Memory Map is used by MemoryInitPei Module to initialize the MMU on your platform.

  @param[out]  VirtualMemoryMap  Array of ARM_MEMORY_REGION_DESCRIPTOR describing a Physical-to-
                                 Virtual Memory mapping. This array must be ended by a zero-filled
                                 entry
**/
VOID
ArmPlatformGetVirtualMemoryMap (
  OUT ARM_MEMORY_REGION_DESCRIPTOR** VirtualMemoryMap
  )
{
  ARM_MEMORY_REGION_ATTRIBUTES  CacheAttributes;
  UINTN                         Index;
  UINTN                         Index1;
  ARM_MEMORY_REGION_DESCRIPTOR  *VirtualMemoryTable;
  UINT8                         *MemBuffer;
  PSSI_STATUS                   PssiStatus;
  PHYTIUM_CPU_MAP_INFO          *CpuMapInfo;
  UINTN                         Size;
  UINT64                        *CpuMap;
  PHYTIUM_PCI_HOST_BRIDGE       *HostBridge;
  PHYTIUM_PCI_HOST_BLOCK        *PciHostBlock;
  PHYTIUM_MEMORY_INFO           *MemInfo;
  PHYTIUM_MEMORY_BLOCK          *MemBlock;
  EFI_RESOURCE_ATTRIBUTE_TYPE   ResourceAttributes;
  //PHYTIUM_MEMORY_BLOCK          MmMemBlock;

  Index = 0;
  MemBlock = NULL;
  PssiStatus = PSSI_SUCCESS;

  VirtualMemoryTable = (ARM_MEMORY_REGION_DESCRIPTOR*)AllocatePages (
                                                        EFI_SIZE_TO_PAGES (
                                                          sizeof (ARM_MEMORY_REGION_DESCRIPTOR) * MAX_VIRTUAL_MEMORY_MAP_DESCRIPTORS
                                                        )
                                                      );
  if (VirtualMemoryTable == NULL) {
    return;
  }

  if (FeaturePcdGet (PcdCacheEnable) == TRUE) {
      CacheAttributes = DDR_ATTRIBUTES_CACHED;
  } else {
      CacheAttributes = DDR_ATTRIBUTES_UNCACHED;
  }

  MemBuffer = AllocatePages (ALLOCATE_PAGES);
  if (MemBuffer == NULL) {
    DEBUG ((DEBUG_ERROR, "AllocatePages MemBuffer failed!\n"));
    return;
  } else {
    //
    // Get CPU informations , frequence, cpu map...
    //
    //
    //
    // IO Base
    //
    Size = ALLOCATE_PAGES * EFI_PAGE_SIZE;
    ZeroMem (MemBuffer, ALLOCATE_PAGES * EFI_PAGE_SIZE);
    CpuMapInfo = (PHYTIUM_CPU_MAP_INFO *) MemBuffer;
    PssiStatus = PssiGetCpuMapsInfo (
                   CpuMapInfo,
                   &Size
                   );
    if (!PSSI_ERROR(PssiStatus)) {
      CpuMap = CpuMapInfo->CpuMap;
      DEBUG ((DEBUG_INFO, "CpuInfo Size:%d\n", Size));
      BuildItsInfo(CpuMapInfo->CpuMapCount);
      BuildGuidDataHob (&gPlatformCpuInforGuid, CpuMapInfo, 1024);
      for (Index1 = 0; Index1< CpuMapInfo->CpuMapCount; Index1++) {
        DEBUG ((DEBUG_INFO, "Cpu Map[%d] : 0x%llx\n", Index1, (UINT64)(*CpuMap)));
        CpuMap++;
      }
      for (Index1 = 0;  Index1< CpuMapInfo->CpuMapCount; Index1++) {
        DEBUG ((DEBUG_INFO, "Add DIE[%d] IO Base\n", Index1));
        VirtualMemoryTable[Index].PhysicalBase   =
          PcdGet64 (PcdSystemIoBase) + Index1 * 0x100000000000;
        VirtualMemoryTable[Index].VirtualBase    =
          PcdGet64 (PcdSystemIoBase) + Index1 * 0x100000000000;
        VirtualMemoryTable[Index].Length         = PcdGet64 (PcdSystemIoSize);
        VirtualMemoryTable[Index++].Attributes     = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE;
      }
    } else {
      DEBUG ((DEBUG_ERROR, "Get Cpu Maps Information failed - %x", PssiStatus));
    }
    //
    // PCIe Host bridge
    //
    Size = ALLOCATE_PAGES * EFI_PAGE_SIZE;
    ZeroMem (MemBuffer, ALLOCATE_PAGES * EFI_PAGE_SIZE);
    PssiStatus = PssiGetPciHostBridgeInfo (
                   (PHYTIUM_PCI_HOST_BRIDGE *) MemBuffer,
                   &Size
                   );
    if (!PSSI_ERROR(PssiStatus)) {
      DEBUG ((DEBUG_INFO, "PciHostInfo Size:%d\n", Size));
      BuildGuidDataHob (&gPlatformPciHostInforGuid, MemBuffer, 1024);
      HostBridge = (PHYTIUM_PCI_HOST_BRIDGE *) MemBuffer;
      DEBUG ((DEBUG_INFO, "PCI host bridge count : %d\n", HostBridge->PciHostCount));
      PciHostBlock = HostBridge->PciHostBlock;
      for (Index1 = 0; Index1 < HostBridge->PciHostCount; Index1++) {
      //for (Index1 = 0; Index1 < 1; Index1++) {
        DEBUG ((DEBUG_INFO, "Pci Config Base : 0x%llx\n", PciHostBlock->PciConfigBase));
        DEBUG ((DEBUG_INFO, "Pci Io Base : 0x%llx\n", PciHostBlock->IoBase));
        DEBUG ((DEBUG_INFO, "Pci Io Size : 0x%llx\n", PciHostBlock->IoSize));
        DEBUG ((DEBUG_INFO, "Pci Mem32 Base : 0x%llx\n", PciHostBlock->Mem32Base));
        DEBUG ((DEBUG_INFO, "Pci Mem32 Size : 0x%llx\n", PciHostBlock->Mem32Size));
        DEBUG ((DEBUG_INFO, "Pci Mem64 Base : 0x%llx\n", PciHostBlock->Mem64Base));
        DEBUG ((DEBUG_INFO, "Pci Mem64 Size : 0x%llx\n", PciHostBlock->Mem64Size));
        //
        //Pci Config Base
        //
        VirtualMemoryTable[Index].PhysicalBase = PciHostBlock->PciConfigBase;
        VirtualMemoryTable[Index].VirtualBase  = PciHostBlock->PciConfigBase;
        VirtualMemoryTable[Index].Length       = PcdGet64 (PcdPciConfigSize);
        VirtualMemoryTable[Index++].Attributes = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE;
        //
        //Pci Io Base
        //
        VirtualMemoryTable[Index].PhysicalBase = PciHostBlock->IoBase;
        VirtualMemoryTable[Index].VirtualBase  = PciHostBlock->IoBase;
        VirtualMemoryTable[Index].Length       = PciHostBlock->IoSize;
        VirtualMemoryTable[Index++].Attributes = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE;
        //
        //Pci Mem32 Base
        //
        VirtualMemoryTable[Index].PhysicalBase = PciHostBlock->Mem32Base;
        VirtualMemoryTable[Index].VirtualBase  = PciHostBlock->Mem32Base;
        VirtualMemoryTable[Index].Length       = PciHostBlock->Mem32Size;
        VirtualMemoryTable[Index++].Attributes = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE;
        //
        //Pci Mem64 Base
        //
        VirtualMemoryTable[Index].PhysicalBase = PciHostBlock->Mem64Base;
        VirtualMemoryTable[Index].VirtualBase  = PciHostBlock->Mem64Base;
        VirtualMemoryTable[Index].Length       = PciHostBlock->Mem64Size;
        VirtualMemoryTable[Index++].Attributes = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE;
        PciHostBlock++;
      }
      DEBUG ((DEBUG_INFO, ""));
    } else {
      DEBUG ((DEBUG_ERROR, "Get Pci Host Bridge Failed!\n"));
    }
    //
    // Memory
    //
    Size = ALLOCATE_PAGES * EFI_PAGE_SIZE;
    ZeroMem (MemBuffer, ALLOCATE_PAGES * EFI_PAGE_SIZE);
    PssiStatus = PssiGetMemRegions (
                   (PHYTIUM_MEMORY_INFO *) MemBuffer,
                   &Size
                   );
    if (!PSSI_ERROR(PssiStatus)) {
      //BuildGuidDataHob (&gPlatformMemoryInforGuid, MemBuffer, 1024);
      MemInfo = (PHYTIUM_MEMORY_INFO *) MemBuffer;
      DEBUG ((DEBUG_INFO, "Memory Block Count : %d\n", MemInfo->MemBlockCount));
      ResourceAttributes =
        EFI_RESOURCE_ATTRIBUTE_PRESENT |
        EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
        EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE |
        EFI_RESOURCE_ATTRIBUTE_WRITE_COMBINEABLE |
        EFI_RESOURCE_ATTRIBUTE_WRITE_THROUGH_CACHEABLE |
        EFI_RESOURCE_ATTRIBUTE_WRITE_BACK_CACHEABLE |
        EFI_RESOURCE_ATTRIBUTE_TESTED;
      DEBUG ((DEBUG_INFO, "MemBlock:%x, ResourceAttributes: %x\n", MemBlock, ResourceAttributes));
      MemBlock = (PHYTIUM_MEMORY_BLOCK *) MemInfo->MemBlock;
      for (Index1 = 0; Index1 < MemInfo->MemBlockCount; Index1++) {
        DEBUG ((DEBUG_INFO,
                "Memory %d, MemStart : 0x%llx, MemSize : 0x%llx, MemNodeId : %llx\n",
                Index1, MemBlock->MemStart, MemBlock->MemSize, MemBlock->MemNodeId
                ));
        //
        //reserved RAS Memory
        //
        if ((MemBlock->MemStart == 0x80000000) && (MemBlock->MemSize == 0x7c000000)) {
          MemBlock->MemSize = 0x7B000000;
        }
        BuildResourceDescriptorHob (
          EFI_RESOURCE_SYSTEM_MEMORY,
          ResourceAttributes,
          MemBlock->MemStart,
          MemBlock->MemSize
          );
        VirtualMemoryTable[Index].PhysicalBase = MemBlock->MemStart;
        VirtualMemoryTable[Index].VirtualBase  = MemBlock->MemStart;
        VirtualMemoryTable[Index].Length       = MemBlock->MemSize;
        VirtualMemoryTable[Index++].Attributes = CacheAttributes;
        MemBlock++;
      }
#if 0
      //
      //Add MM Communicae Buffer Memory to hob
      //
      ZeroMem (&MmMemBlock, sizeof (PHYTIUM_MEMORY_BLOCK));
      MmMemBlock.MemStart = PcdGet64 (PcdMmBufferBase);
      MmMemBlock.MemSize = PcdGet64 (PcdMmBufferSize);
      MmMemBlock.MemNodeId = 0;
      MemInfo = (PHYTIUM_MEMORY_INFO *) MemBuffer;
      CopyMem (((UINT8 *) MemBuffer) + 8 + MemInfo->MemBlockCount * sizeof (PHYTIUM_MEMORY_BLOCK), (VOID *) &MmMemBlock, sizeof (PHYTIUM_MEMORY_BLOCK));
      MemInfo->MemBlockCount += 1;
      DEBUG ((DEBUG_INFO, "Mem Region Final:\n"));
      MemBlock = (PHYTIUM_MEMORY_BLOCK *) MemInfo->MemBlock;
      for (Index1 = 0; Index1 < MemInfo->MemBlockCount; Index1++) {
        //
        //Add RAS Memory to hob
        //
        if ((MemBlock->MemStart == 0x80000000) && (MemBlock->MemSize == 0x7B000000)) {
          MemBlock->MemSize = 0x7C000000;
        }
        DEBUG ((DEBUG_INFO,
                "Memory %d, MemStart : 0x%llx, MemSize : 0x%llx, MemNodeId : %llx\n",
                Index1, MemBlock->MemStart, MemBlock->MemSize, MemBlock->MemNodeId
                ));
      }
#endif
      BuildGuidDataHob (&gPlatformMemoryInforGuid, MemBuffer, 1024);
    } else {
      DEBUG ((DEBUG_ERROR, "Get Memory Region Failed!\n"));
    }
  }

  // Qspi Flash
  VirtualMemoryTable[Index].PhysicalBase = 0;
  VirtualMemoryTable[Index].VirtualBase    = 0;
  VirtualMemoryTable[Index].Length         = 0x2000000;
  VirtualMemoryTable[Index++].Attributes     = ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK_NONSHAREABLE;
  // End of Table
  VirtualMemoryTable[Index].PhysicalBase = 0;
  VirtualMemoryTable[Index].VirtualBase    = 0;
  VirtualMemoryTable[Index].Length         = 0;
  VirtualMemoryTable[Index].Attributes     = (ARM_MEMORY_REGION_ATTRIBUTES)0;

  ASSERT((Index + 1) <= MAX_VIRTUAL_MEMORY_MAP_DESCRIPTORS);

  for (Index1 = 0; Index1 < Index; Index1++) {
    DEBUG ((DEBUG_INFO,
            "PhysicalBase %llx VirtualBase %llx Length %llx Attributes %llx\n",
            VirtualMemoryTable[Index1].PhysicalBase,
            VirtualMemoryTable[Index1].VirtualBase,
            VirtualMemoryTable[Index1].Length,
            VirtualMemoryTable[Index1].Attributes));
  }

  if (MemBuffer != NULL) {
    FreePages (MemBuffer, ALLOCATE_PAGES);
  }

  BuildCpuAcpiInfoHob();

  *VirtualMemoryMap = VirtualMemoryTable;
}

